package middleware

import (
	"devOpsApi/common/util"
	"devOpsApi/model"
	"encoding/json"
	"errors"
	"github.com/bitly/go-simplejson"
	"github.com/docker/docker/api/types"
	"github.com/kataras/iris/websocket"
	"io"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

var XtermInitOnce sync.Once
var XtermWsServerRs *XtermIriswsServer //结构体全局变量 方便其他地方调用
type XtermIriswsServer struct {
	ServerRs         *websocket.Server
	DockerCli        *util.DockerCli
	pipeline         *model.Pipeline
	HijackedResponse map[string]*types.HijackedResponse
}

//发送消息格式
type WsRequest struct {
	Show string `json:"show"`
}

//接受消息格式
type WsResponse struct {
	Cmd string `json:"cmd"`
}

/**
注册webscoket
*/
func GetXtermWsServer() *XtermIriswsServer {
	XtermInitOnce.Do(func() {
		XtermWsServerRs = &XtermIriswsServer{
			HijackedResponse: make(map[string]*types.HijackedResponse),
			DockerCli:        util.NewDockerCli(),
			pipeline:         model.NewPipeline(),
		}
		XtermWsServerRs.ServerRs = websocket.New(websocket.Config{
			EvtMessagePrefix: []byte(""),
			ReadBufferSize:   1024, //指定读缓存区大小
			WriteBufferSize:  1024, // 指定写缓存区大小
			CheckOrigin: func(r *http.Request) bool { //解决跨域问题
				return true
			},
		})
	})
	return XtermWsServerRs
}

/**
数据接收
*/
func (w *XtermIriswsServer) ReceiveMessage() {
	//绑定接收数据处理
	w.ServerRs.OnConnection(func(ws websocket.Connection) {
		projectId := ws.Context().URLParamDefault("project_id", "")
		if projectId == "" {
			_ = ws.Disconnect()
			return
		}
		containerId, err := w.StartContainer(projectId, ws)
		if err != nil {
			_ = w.WsRequestMsg(ws, err.Error())
			_ = ws.Disconnect()
			return
		}
		ws.SetValue("containerId", containerId)
		ws.OnMessage(func(data []byte) {
			var wsResponse WsResponse
			err := json.Unmarshal(data, &wsResponse)
			if err == nil {
				if wsResponse.Cmd == "_ping_" {
					_ = w.WsRequestMsg(ws, "_pong_")
					return
				}
				_, err = io.Copy(w.HijackedResponse[ws.ID()].Conn, strings.NewReader(wsResponse.Cmd))
				if err != nil {
					_ = w.WsRequestMsg(ws, "错误信息: "+err.Error()+"\r\n")
					return
				}
			}
		})
		//ws.OnError(func(e error) {
		//	go w.WsLeave(ws)
		//})
		ws.OnDisconnect(func() {
			go w.WsLeave(ws)
		})
	})
}

func (w *XtermIriswsServer) WsLeave(ws websocket.Connection) {
	if _, ok := w.HijackedResponse[ws.ID()]; ok {
		lock.Lock()
		delete(w.HijackedResponse, ws.ID())
		lock.Unlock()
	}

	if ws.GetValueString("containerId") != "" {
		_ = w.DockerCli.Client.ContainerStop(w.DockerCli.Ctx, ws.GetValueString("containerId"), nil)
	}
	//_ = w.DockerCli.Client.Close()
}

func (w *XtermIriswsServer) WsRequestMsg(conn websocket.Connection, msg string) error {
	var wsRequest WsRequest
	wsRequest.Show = msg
	message, _ := json.Marshal(wsRequest)
	err := conn.EmitMessage(message)
	return err
}

func (w *XtermIriswsServer) StartContainer(projectId string, ws websocket.Connection) (containerId string, err error) {
	projectIdUint, err := strconv.Atoi(projectId)
	if err != nil {
		return
	}
	pipeline, err := w.pipeline.DetailAnyOne(uint(projectIdUint))
	if err != nil {
		err = errors.New("Error: 记录未找到！")
		return
	}
	containerName := w.createContainerName(pipeline)
	cli := w.DockerCli
	detail, err := cli.Client.ContainerInspect(cli.Ctx, containerName)
	if err != nil {
		err = errors.New("Error: 该项目容器不存在,请先执行该流水线！")
		return
	}
	if detail.ContainerJSONBase == nil {
		err = errors.New("Error: 获取容器信息失败！")
		return
	}
	containerState := detail.ContainerJSONBase.State.Status
	containerId = detail.ContainerJSONBase.ID
	if containerState != "running" {
		err = cli.Client.ContainerStart(cli.Ctx, containerId, types.ContainerStartOptions{})
		if err != nil {
			err = errors.New("启动容器【" + containerId + "】失败 : " + err.Error())
			return
		}
	}

	EnvsJson := simplejson.New()
	if string(pipeline.Envs.([]byte)) != "" {
		EnvsJson, err = simplejson.NewJson(pipeline.Envs.([]byte))
		if err != nil {
			err = errors.New("全局参数解析失败：" + err.Error())
			return
		}
	}

	var envData []string
	envList, _ := EnvsJson.Array()
	for _, list := range envList {
		data := list.(map[string]interface{})
		if data["name"] != nil && data["value"] != nil {
			envData = append(envData, data["name"].(string)+"="+data["value"].(string))
		}
	}

	execConfig := types.ExecConfig{
		AttachStdout: true,
		AttachStderr: true,
		Cmd:          []string{"/bin/bash"},
		Tty:          true,
		AttachStdin:  true,
		Env:          envData,
	}
	Response, err := cli.Client.ContainerExecCreate(cli.Ctx, containerId, execConfig)
	if err != nil {
		return "", err
	}
	ws.SetValue("IDResponse", Response.ID)
	w.Attach(ws)
	return
}

func (w *XtermIriswsServer) Attach(ws websocket.Connection) {
	attachOpts := types.ExecStartCheck{Tty: true}
	hijack, _ := w.DockerCli.Client.ContainerExecAttach(w.DockerCli.Ctx, ws.GetValueString("IDResponse"), attachOpts)
	lock.Lock()
	w.HijackedResponse[ws.ID()] = &hijack
	lock.Unlock()
	_ = w.DockerCli.Client.ContainerResize(w.DockerCli.Ctx, ws.GetValueString("IDResponse"), types.ResizeOptions{Width: 960})

	if _, ok := w.HijackedResponse[ws.ID()]; ok {
		lock.Lock()
		go w.WatchContainerOut(w.HijackedResponse[ws.ID()], ws)
		lock.Unlock()
	}
}

func (w *XtermIriswsServer) WatchContainerOut(hijackRes *types.HijackedResponse, ws websocket.Connection) {
	for {
		if _, ok := w.HijackedResponse[ws.ID()]; !ok {
			return
		}
		b := make([]byte, 4096)
		n, err := w.HijackedResponse[ws.ID()].Reader.Read(b)
		if err != nil {
			return
		}
		b = b[:n]
		_ = w.WsRequestMsg(ws, string(b))
	}
}

//根据规则生成容器名称
func (w *XtermIriswsServer) createContainerName(pipeline model.Pipeline) string {
	containerName := pipeline.Language + "_" + strconv.Itoa(int(pipeline.Uid)) + "_" + strconv.Itoa(int(pipeline.ID))
	return containerName
}
